import FillStylePattern from './FillStylePattern'
import FillStyleLinearGradient from './FillStyleLinearGradient'
import FillStyleRadialGradient from './FillStyleRadialGradient'
import GImage from '../env/image.js'
import { ArrayBufferToBase64, Base64ToUint8ClampedArray } from '../env/tool.js'

export default class CanvasRenderingContext2D {
  _drawCommands = ''

  _globalAlpha = 1.0

  _fillStyle = 'rgb(0,0,0)'
  _strokeStyle = 'rgb(0,0,0)'

  _lineWidth = 1
  _lineCap = 'butt'
  _lineJoin = 'miter'

  _miterLimit = 10

  _globalCompositeOperation = 'source-over'

  _textAlign = 'start'
  _textBaseline = 'alphabetic'

  _font = '10px sans-serif'

  _savedGlobalAlpha = []

  timer = null
  componentId = null

  _notCommitDrawImageCache = []
  _needRedrawImageCache = []
  _redrawCommands = ''
  _autoSaveContext = true
  // _imageMap = new GHashMap();
  // _textureMap = new GHashMap();

  constructor() {
    this.className = 'CanvasRenderingContext2D'
    //this.save()
  }

  setFillStyle(value) {
    this.fillStyle = value
  }

  set fillStyle(value) {
    this._fillStyle = value

    if (typeof value == 'string') {
      this._drawCommands = this._drawCommands.concat('F' + value + ';')
    } else if (value instanceof FillStylePattern) {
      const image = value._img
      if (!image.complete) {
        image.onload = () => {
          var index = this._needRedrawImageCache.indexOf(image)
          if (index > -1) {
            this._needRedrawImageCache.splice(index, 1)
            CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id)
            this._redrawflush(true)
          }
        }
        this._notCommitDrawImageCache.push(image)
      } else {
        CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id)
      }

      //CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
      this._drawCommands = this._drawCommands.concat('G' + image._id + ',' + value._style + ';')
    } else if (value instanceof FillStyleLinearGradient) {
      var command =
        'D' +
        value._start_pos._x.toFixed(2) +
        ',' +
        value._start_pos._y.toFixed(2) +
        ',' +
        value._end_pos._x.toFixed(2) +
        ',' +
        value._end_pos._y.toFixed(2) +
        ',' +
        value._stop_count
      for (var i = 0; i < value._stop_count; ++i) {
        command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color
      }
      this._drawCommands = this._drawCommands.concat(command + ';')
    } else if (value instanceof FillStyleRadialGradient) {
      var command =
        'H' +
        value._start_pos._x.toFixed(2) +
        ',' +
        value._start_pos._y.toFixed(2) +
        ',' +
        value._start_pos._r.toFixed(2) +
        ',' +
        value._end_pos._x.toFixed(2) +
        ',' +
        value._end_pos._y.toFixed(2) +
        ',' +
        value._end_pos._r.toFixed(2) +
        ',' +
        value._stop_count
      for (var i = 0; i < value._stop_count; ++i) {
        command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color
      }
      this._drawCommands = this._drawCommands.concat(command + ';')
    }
  }

  get fillStyle() {
    return this._fillStyle
  }

  get globalAlpha() {
    return this._globalAlpha
  }

  setGlobalAlpha(value) {
    this.globalAlpha = value
  }

  set globalAlpha(value) {
    this._globalAlpha = value
    this._drawCommands = this._drawCommands.concat('a' + value.toFixed(2) + ';')
  }

  get strokeStyle() {
    return this._strokeStyle
  }

  setStrokeStyle(value) {
    this.strokeStyle = value
  }

  set strokeStyle(value) {
    this._strokeStyle = value

    if (typeof value == 'string') {
      this._drawCommands = this._drawCommands.concat('S' + value + ';')
    } else if (value instanceof FillStylePattern) {
      CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id)
      this._drawCommands = this._drawCommands.concat('G' + image._id + ',' + value._style + ';')
    } else if (value instanceof FillStyleLinearGradient) {
      var command =
        'D' +
        value._start_pos._x.toFixed(2) +
        ',' +
        value._start_pos._y.toFixed(2) +
        ',' +
        value._end_pos._x.toFixed(2) +
        ',' +
        value._end_pos._y.toFixed(2) +
        ',' +
        value._stop_count

      for (var i = 0; i < value._stop_count; ++i) {
        command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color
      }
      this._drawCommands = this._drawCommands.concat(command + ';')
    } else if (value instanceof FillStyleRadialGradient) {
      var command =
        'H' +
        value._start_pos._x.toFixed(2) +
        ',' +
        value._start_pos._y.toFixed(2) +
        ',' +
        value._start_pos._r.toFixed(2) +
        ',' +
        value._end_pos._x.toFixed(2) +
        ',' +
        value._end_pos._y +
        ','.toFixed(2) +
        value._end_pos._r.toFixed(2) +
        ',' +
        value._stop_count

      for (var i = 0; i < value._stop_count; ++i) {
        command += ',' + value._stops[i]._pos + ',' + value._stops[i]._color
      }
      this._drawCommands = this._drawCommands.concat(command + ';')
    }
  }

  get lineWidth() {
    return this._lineWidth
  }

  setLineWidth(value) {
    this.lineWidth = value
  }

  set lineWidth(value) {
    this._lineWidth = value
    this._drawCommands = this._drawCommands.concat('W' + value + ';')
  }

  get lineCap() {
    return this._lineCap
  }

  setLineCap(value) {
    this.lineCap = value
  }

  set lineCap(value) {
    this._lineCap = value
    this._drawCommands = this._drawCommands.concat('C' + value + ';')
  }

  get lineJoin() {
    return this._lineJoin
  }

  setLineJoin(value) {
    this.lineJoin = value
  }

  set lineJoin(value) {
    this._lineJoin = value
    this._drawCommands = this._drawCommands.concat('J' + value + ';')
  }

  get miterLimit() {
    return this._miterLimit
  }

  setMiterLimit(value) {
    this.miterLimit = value
  }

  set miterLimit(value) {
    this._miterLimit = value
    this._drawCommands = this._drawCommands.concat('M' + value + ';')
  }

  get globalCompositeOperation() {
    return this._globalCompositeOperation
  }

  set globalCompositeOperation(value) {
    this._globalCompositeOperation = value
    let mode = 0
    switch (value) {
      case 'source-over':
        mode = 0
        break
      case 'source-atop':
        mode = 5
        break
      case 'source-in':
        mode = 0
        break
      case 'source-out':
        mode = 2
        break
      case 'destination-over':
        mode = 4
        break
      case 'destination-atop':
        mode = 4
        break
      case 'destination-in':
        mode = 4
        break
      case 'destination-out':
        mode = 3
        break
      case 'lighter':
        mode = 1
        break
      case 'copy':
        mode = 2
        break
      case 'xor':
        mode = 6
        break
      default:
        mode = 0
    }

    this._drawCommands = this._drawCommands.concat('B' + mode + ';')
  }

  get textAlign() {
    return this._textAlign
  }

  setTextAlign(value) {
    this.textAlign = value
  }

  set textAlign(value) {
    this._textAlign = value
    let Align = 0
    switch (value) {
      case 'start':
        Align = 0
        break
      case 'end':
        Align = 1
        break
      case 'left':
        Align = 2
        break
      case 'center':
        Align = 3
        break
      case 'right':
        Align = 4
        break
      default:
        Align = 0
    }

    this._drawCommands = this._drawCommands.concat('A' + Align + ';')
  }

  get textBaseline() {
    return this._textBaseline
  }

  setTextBaseline(value) {
    this.textBaseline = value
  }

  set textBaseline(value) {
    this._textBaseline = value
    let baseline = 0
    switch (value) {
      case 'alphabetic':
        baseline = 0
        break
      case 'middle':
        baseline = 1
        break
      case 'top':
        baseline = 2
        break
      case 'hanging':
        baseline = 3
        break
      case 'bottom':
        baseline = 4
        break
      case 'ideographic':
        baseline = 5
        break
      default:
        baseline = 0
        break
    }

    this._drawCommands = this._drawCommands.concat('E' + baseline + ';')
  }

  get font() {
    return this._font
  }

  setFontSize(size) {
    var str = this._font
    var strs = str.trim().split(/\s+/)
    for (var i = 0; i < strs.length; i++) {
      var values = [
        'normal',
        'italic',
        'oblique',
        'normal',
        'small-caps',
        'normal',
        'bold',
        'bolder',
        'lighter',
        '100',
        '200',
        '300',
        '400',
        '500',
        '600',
        '700',
        '800',
        '900',
        'normal',
        'ultra-condensed',
        'extra-condensed',
        'condensed',
        'semi-condensed',
        'semi-expanded',
        'expanded',
        'extra-expanded',
        'ultra-expanded',
      ]

      if (-1 == values.indexOf(strs[i].trim())) {
        if (typeof size === 'string') {
          strs[i] = size
        } else if (typeof size === 'number') {
          strs[i] = String(size) + 'px'
        }
        break
      }
    }
    this.font = strs.join(' ')
  }

  set font(value) {
    this._font = value
    this._drawCommands = this._drawCommands.concat('j' + value + ';')
  }

  setTransform(a, b, c, d, tx, ty) {
    this._drawCommands = this._drawCommands.concat(
      't' +
        (a === 1 ? '1' : a.toFixed(2)) +
        ',' +
        (b === 0 ? '0' : b.toFixed(2)) +
        ',' +
        (c === 0 ? '0' : c.toFixed(2)) +
        ',' +
        (d === 1 ? '1' : d.toFixed(2)) +
        ',' +
        tx.toFixed(2) +
        ',' +
        ty.toFixed(2) +
        ';',
    )
  }

  transform(a, b, c, d, tx, ty) {
    this._drawCommands = this._drawCommands.concat(
      'f' +
        (a === 1 ? '1' : a.toFixed(2)) +
        ',' +
        (b === 0 ? '0' : b.toFixed(2)) +
        ',' +
        (c === 0 ? '0' : c.toFixed(2)) +
        ',' +
        (d === 1 ? '1' : d.toFixed(2)) +
        ',' +
        tx +
        ',' +
        ty +
        ';',
    )
  }

  resetTransform() {
    this._drawCommands = this._drawCommands.concat('m;')
  }

  scale(a, d) {
    this._drawCommands = this._drawCommands.concat('k' + a.toFixed(2) + ',' + d.toFixed(2) + ';')
  }

  rotate(angle) {
    this._drawCommands = this._drawCommands.concat('r' + angle.toFixed(6) + ';')
  }

  translate(tx, ty) {
    this._drawCommands = this._drawCommands.concat('l' + tx.toFixed(2) + ',' + ty.toFixed(2) + ';')
  }

  save() {
    this._savedGlobalAlpha.push(this._globalAlpha)
    this._drawCommands = this._drawCommands.concat('v;')
  }

  restore() {
    this._drawCommands = this._drawCommands.concat('e;')
    this._globalAlpha = this._savedGlobalAlpha.pop()
  }

  createPattern(img, pattern) {
    if (typeof img === 'string') {
      var imgObj = new GImage()
      imgObj.src = img
      img = imgObj
    }
    return new FillStylePattern(img, pattern)
  }

  createLinearGradient(x0, y0, x1, y1) {
    return new FillStyleLinearGradient(x0, y0, x1, y1)
  }

  createRadialGradient = function (x0, y0, r0, x1, y1, r1) {
    return new FillStyleRadialGradient(x0, y0, r0, x1, y1, r1)
  }

  createCircularGradient = function (x0, y0, r0) {
    return new FillStyleRadialGradient(x0, y0, 0, x0, y0, r0)
  }

  strokeRect(x, y, w, h) {
    this._drawCommands = this._drawCommands.concat('s' + x + ',' + y + ',' + w + ',' + h + ';')
  }

  clearRect(x, y, w, h) {
    this._drawCommands = this._drawCommands.concat('c' + x + ',' + y + ',' + w + ',' + h + ';')
  }

  clip() {
    this._drawCommands = this._drawCommands.concat('p;')
  }

  resetClip() {
    this._drawCommands = this._drawCommands.concat('q;')
  }

  closePath() {
    this._drawCommands = this._drawCommands.concat('o;')
  }

  moveTo(x, y) {
    this._drawCommands = this._drawCommands.concat('g' + x.toFixed(2) + ',' + y.toFixed(2) + ';')
  }

  lineTo(x, y) {
    this._drawCommands = this._drawCommands.concat('i' + x.toFixed(2) + ',' + y.toFixed(2) + ';')
  }

  quadraticCurveTo = function (cpx, cpy, x, y) {
    this._drawCommands = this._drawCommands.concat('u' + cpx + ',' + cpy + ',' + x + ',' + y + ';')
  }

  bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
    this._drawCommands = this._drawCommands.concat(
      'z' +
        cp1x.toFixed(2) +
        ',' +
        cp1y.toFixed(2) +
        ',' +
        cp2x.toFixed(2) +
        ',' +
        cp2y.toFixed(2) +
        ',' +
        x.toFixed(2) +
        ',' +
        y.toFixed(2) +
        ';',
    )
  }

  arcTo(x1, y1, x2, y2, radius) {
    this._drawCommands = this._drawCommands.concat('h' + x1 + ',' + y1 + ',' + x2 + ',' + y2 + ',' + radius + ';')
  }

  beginPath() {
    this._drawCommands = this._drawCommands.concat('b;')
  }

  fillRect(x, y, w, h) {
    this._drawCommands = this._drawCommands.concat('n' + x + ',' + y + ',' + w + ',' + h + ';')
  }

  rect(x, y, w, h) {
    this._drawCommands = this._drawCommands.concat('w' + x + ',' + y + ',' + w + ',' + h + ';')
  }

  fill() {
    this._drawCommands = this._drawCommands.concat('L;')
  }

  stroke(path) {
    this._drawCommands = this._drawCommands.concat('x;')
  }

  arc(x, y, radius, startAngle, endAngle, anticlockwise) {
    let ianticlockwise = 0
    if (anticlockwise) {
      ianticlockwise = 1
    }

    this._drawCommands = this._drawCommands.concat(
      'y' +
        x.toFixed(2) +
        ',' +
        y.toFixed(2) +
        ',' +
        radius.toFixed(2) +
        ',' +
        startAngle +
        ',' +
        endAngle +
        ',' +
        ianticlockwise +
        ';',
    )
  }

  fillText(text, x, y) {
    let tmptext = text.replace(/!/g, '!!')
    tmptext = tmptext.replace(/,/g, '!,')
    tmptext = tmptext.replace(/;/g, '!;')
    this._drawCommands = this._drawCommands.concat('T' + tmptext + ',' + x + ',' + y + ',0.0;')
  }

  strokeText = function (text, x, y) {
    let tmptext = text.replace(/!/g, '!!')
    tmptext = tmptext.replace(/,/g, '!,')
    tmptext = tmptext.replace(/;/g, '!;')
    this._drawCommands = this._drawCommands.concat('U' + tmptext + ',' + x + ',' + y + ',0.0;')
  }

  measureText(text) {
    return CanvasRenderingContext2D.GBridge.measureText(text, this.font, this.componentId)
  }

  isPointInPath = function (x, y) {
    throw new Error('GCanvas not supported yet')
  }

  drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
    if (typeof image === 'string') {
      var imgObj = new GImage()
      imgObj.src = image
      image = imgObj
    }
    if (image instanceof GImage) {
      if (!image.complete) {
        imgObj.onload = () => {
          var index = this._needRedrawImageCache.indexOf(image)
          if (index > -1) {
            this._needRedrawImageCache.splice(index, 1)
            CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id)
            this._redrawflush(true)
          }
        }
        this._notCommitDrawImageCache.push(image)
      } else {
        CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id)
      }
      var srcArgs = [image, sx, sy, sw, sh, dx, dy, dw, dh]
      var args = []
      for (var arg in srcArgs) {
        if (typeof srcArgs[arg] != 'undefined') {
          args.push(srcArgs[arg])
        }
      }
      this.__drawImage.apply(this, args)
      //this.__drawImage(image,sx, sy, sw, sh, dx, dy, dw, dh);
    }
  }

  __drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
    const numArgs = arguments.length

    function drawImageCommands() {
      if (numArgs === 3) {
        const x = parseFloat(sx) || 0.0
        const y = parseFloat(sy) || 0.0

        return (
          'd' +
          image._id +
          ',0,0,' +
          image.width +
          ',' +
          image.height +
          ',' +
          x +
          ',' +
          y +
          ',' +
          image.width +
          ',' +
          image.height +
          ';'
        )
      } else if (numArgs === 5) {
        const x = parseFloat(sx) || 0.0
        const y = parseFloat(sy) || 0.0
        const width = parseInt(sw) || image.width
        const height = parseInt(sh) || image.height

        return (
          'd' +
          image._id +
          ',0,0,' +
          image.width +
          ',' +
          image.height +
          ',' +
          x +
          ',' +
          y +
          ',' +
          width +
          ',' +
          height +
          ';'
        )
      } else if (numArgs === 9) {
        sx = parseFloat(sx) || 0.0
        sy = parseFloat(sy) || 0.0
        sw = parseInt(sw) || image.width
        sh = parseInt(sh) || image.height
        dx = parseFloat(dx) || 0.0
        dy = parseFloat(dy) || 0.0
        dw = parseInt(dw) || image.width
        dh = parseInt(dh) || image.height

        return (
          'd' + image._id + ',' + sx + ',' + sy + ',' + sw + ',' + sh + ',' + dx + ',' + dy + ',' + dw + ',' + dh + ';'
        )
      }
    }
    this._drawCommands += drawImageCommands()
  }

  _flush(reserve, callback) {
    const commands = this._drawCommands
    this._drawCommands = ''
    CanvasRenderingContext2D.GBridge.render2d(this.componentId, commands, callback)
    this._needRender = false
  }

  _redrawflush(reserve, callback) {
    const commands = this._redrawCommands
    CanvasRenderingContext2D.GBridge.render2d(this.componentId, commands, callback)
    if (this._needRedrawImageCache.length == 0) {
      this._redrawCommands = ''
    }
  }

  draw(reserve, callback) {
    if (!reserve) {
      this._globalAlpha = this._savedGlobalAlpha.pop()
      this._savedGlobalAlpha.push(this._globalAlpha)
      this._redrawCommands = this._drawCommands
      this._needRedrawImageCache = this._notCommitDrawImageCache
      if (this._autoSaveContext) {
        this._drawCommands = 'v;' + this._drawCommands
        this._autoSaveContext = false
      } else {
        this._drawCommands = 'e;X;v;' + this._drawCommands
      }
    } else {
      this._needRedrawImageCache = this._needRedrawImageCache.concat(this._notCommitDrawImageCache)
      this._redrawCommands += this._drawCommands
      if (this._autoSaveContext) {
        this._drawCommands = 'v;' + this._drawCommands
        this._autoSaveContext = false
      }
    }
    this._notCommitDrawImageCache = []
    if (this._flush) {
      this._flush(reserve, callback)
    }
  }

  getImageData(x, y, w, h, callback) {
    CanvasRenderingContext2D.GBridge.getImageData(this.componentId, x, y, w, h, function (res) {
      res.data = Base64ToUint8ClampedArray(res.data)
      if (typeof callback == 'function') {
        callback(res)
      }
    })
  }

  putImageData(data, x, y, w, h, callback) {
    if (data instanceof Uint8ClampedArray) {
      data = ArrayBufferToBase64(data)
      CanvasRenderingContext2D.GBridge.putImageData(this.componentId, data, x, y, w, h, function (res) {
        if (typeof callback == 'function') {
          callback(res)
        }
      })
    }
  }

  toTempFilePath(x, y, width, height, destWidth, destHeight, fileType, quality, callback) {
    CanvasRenderingContext2D.GBridge.toTempFilePath(
      this.componentId,
      x,
      y,
      width,
      height,
      destWidth,
      destHeight,
      fileType,
      quality,
      function (res) {
        if (typeof callback == 'function') {
          callback(res)
        }
      },
    )
  }
}
